﻿using System;
using System.IO;
using System.Collections.Generic;
using System.Linq;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Audio;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using System.Xml.Serialization;

namespace HighFantasyRPG
{
   public class Level
    {

        // Physical structure of the level.
        private Tile[,] tiles, sprites;
        private Tile exit;
        private int variation;
        private Texture2D[] layers;
        private Texture2D TileRectangle;
        public Map mapfile;
        private string[,] map, sprs;
        public string Name;
        public float cameraPositionX, cameraPositionY;
        public Vector2 cameraPosition;
        public float cameraMovementX, cameraMovementY;
        public ContentManager Content;
        public List<Object> objects = new List<Object>();
        public List<Enemy> enemies = new List<Enemy>();
        public List<NPC> npcs = new List<NPC>();
        public Player player;
        private Vector2 start;
        private KeyboardState keyboardState;
        private int EntityLayer = 1;
        public MessageBox messagebox;
        public Panel panelarea;
        public int levelIndex;
        public bool Exitreached;
        public int Width
        {
            get { return tiles.GetLength(0); }
        }

        /// <summary>
        /// Height of the level measured in tiles.
        /// </summary>
        public int Height
        {
            get { return tiles.GetLength(1); }
        }

        public TileCollision GetCollision(int x, int y)
        {

            // Prevent escaping past the level ends.
            if (x < 0 || x >= Width)
                return TileCollision.Impassable;
            // Allow jumping past the level top and falling through the bottom.
            if (y < 0 || y >= Height)
                return TileCollision.Impassable;


            if (tiles[x, y] != null)
                {
            

            return tiles[x, y].Collision;

                }

            return TileCollision.Undefined;
        }

        /// <summary>
        /// Gets the bounding rectangle of a tile in world space.
        /// </summary>        
        public Rectangle GetBounds(int x, int y)
        {
            return new Rectangle(x * Tile.Width, y * Tile.Height, Tile.Width, Tile.Height);
        }

        public Level(string name, Stream fileStream, int levelIndex, ContentManager content)
        {
            // Load level file
            this.Name = name;
            this.Content = content;
            this.levelIndex = levelIndex;
            map = new string[50, 50];
            sprs = new string[50, 50];
            panelarea = new Panel(Content);
            messagebox = new MessageBox(Content, 1, 4);
            XmlSerializer ax = new XmlSerializer(typeof(Map));
            mapfile = (Map)ax.Deserialize(fileStream);

            LoadTiles();

            fileStream.Close();
            
            layers = new Texture2D[0];
            for (int i = 0; i < layers.Length; ++i)
            {
                // Choose a random segment if each background layer for level variety.
                int segmentIndex = levelIndex;
                layers[i] = content.Load<Texture2D>("Background/Layer" + i + "_" + segmentIndex);
            }
            TileRectangle = content.Load<Texture2D>("Background/Tile_rect");
            
        }

       

        private void LoadTiles()
        {
            // Load the level and ensure all of the lines are the same length.
            int with = mapfile.rows.Count();
            int height = mapfile.rows[0].columns.Count();

            tiles = new Tile[height, with];
            sprites = new Tile[height, with];
            

            for (int r = 0; r <= mapfile.rows.Count() - 1; r++)
            {
                
                for (int i = 0; i <= mapfile.rows[r].columns.Count() - 1; i++)
                {
                    
                    
                    variation = mapfile.rows[r].columns[i].variation;
                    map[i, r] = Enum.GetName(typeof(Enumeration.TileType), mapfile.rows[r].columns[i].tileType);
                    sprs[i, r] = Enum.GetName(typeof(Enumeration.SpriteType), mapfile.rows[r].columns[i].spriteType);

                    tiles[i, r] = LoadTile(mapfile.rows[r].columns[i].tileType, i, r, mapfile.rows[r].columns[i].variation);
                    sprites[i, r] = LoadSprites(mapfile.rows[r].columns[i].spriteType, i, r);
                }
            }
        }

        private Tile LoadTile(Enumeration.TileType tileType, int x, int y, int variation)
        {
            switch (tileType)
            {

                case Enumeration.TileType.space:


                    return LoadTile("0", 0, TileCollision.Impassable);
                    


                case Enumeration.TileType.grass:

                    return LoadTile("Grass", variation, TileCollision.Passable);


                case Enumeration.TileType.snow:

                    return LoadTile("Snow", variation, TileCollision.Passable);
                
                case Enumeration.TileType.dirt:

                    return LoadTile("Dirt", variation, TileCollision.Passable);

                case Enumeration.TileType.wall:

                    return LoadVariantTile("Walls", 0, variation, TileCollision.Impassable);

                case Enumeration.TileType.roof:

                    return LoadVariantTile("Roof", 0, variation, TileCollision.Impassable);


                case Enumeration.TileType.road:


                    return LoadTile("Road", variation, TileCollision.Passable);

                case Enumeration.TileType.water:

                    return LoadWaterTile("Water", variation, TileCollision.Impassable);

                case Enumeration.TileType.exit:
                    exit = tiles[x, y];
                    return LoadExitTile("Exit", x, y,  TileCollision.Exit);

                default:

                    return null;

           }

         }

        private Tile LoadSprites(Enumeration.SpriteType spriteType, int x, int y)
           {
           switch (spriteType)
            {

                case Enumeration.SpriteType.player:

                    return LoadPlayerTile("Player", x, y);

                case Enumeration.SpriteType.tree:

                    return LoadObjectTile("Tree", x, y, 64, 64, ObjectType.Tree);

                case Enumeration.SpriteType.brush:

                    return LoadObjectTile("Brush", x, y, 32, 32, ObjectType.Brush);

                case Enumeration.SpriteType.chiken:

                    return LoadEnemyTile("Chiken", x, y, 32, 32, EnemyType.Critter);
                case Enumeration.SpriteType.bear:

                    return LoadEnemyTile("Bear", x, y, 32, 32, EnemyType.Beast);

                case Enumeration.SpriteType.vendor:

                    return LoadNPCTile("Vendor", x, y, 64, 64, NPCType.Person);

                case Enumeration.SpriteType.royalty:

                    return LoadNPCTile("Royalty", x, y, 64, 64, NPCType.Person);

                default:

                    return null;

               }

           }

        private Tile LoadTile(string name, int variation,TileCollision collision)
        {
            return new Tile(name, Content.Load<Texture2D>("Tiles/" + name + "/" +  variation), collision);
        }

        private Tile LoadVariantTile(string name, int id, int variation, TileCollision collision)
        {
            return new Tile(name, Content.Load<Texture2D>("Tiles/" + name + "/" + id + "_" + variation), collision);
        }

        private Tile LoadWaterTile(string name, int variation, TileCollision collision)
        {
            return new Tile(name, Content.Load<Texture2D>("Tiles/" + name + "/" + variation), collision);
        }

        private Tile LoadPlayerTile(string name, int x, int y)
        {
            start = RectangleExtensions.GetBottomCenter(GetBounds(x, y));
            player = new Player(this, x, y);

            return new Tile(name, Content.Load<Texture2D>("Tiles/Grass/1"), TileCollision.Passable);
        }

        private Tile LoadEnemyTile(string name,int x, int y, int width, int height, EnemyType type)
        {
            enemies.Add(new Enemy(name, this, x, y, width, height, type));

            return new Tile(name, Content.Load<Texture2D>("Tiles/Grass/1"), TileCollision.Passable);
        }

        private Tile LoadNPCTile(string name, int x, int y, int width, int height, NPCType type)
        {
            npcs.Add(new NPC(name, this, x, y, width, height, type));

            return new Tile(name, Content.Load<Texture2D>("Tiles/Grass/1"), TileCollision.Passable);
        }

        private Tile LoadObjectTile(string name, int x, int y, int width, int height, ObjectType type)
        {
            
            objects.Add(new Object(name, this, x, y, width, height, type));

            return new Tile(name, Content.Load<Texture2D>("Tiles/Grass/1"), TileCollision.Passable);
        }

        private Tile LoadExitTile(string name, int x, int y, TileCollision collision)
        {
           
            return new Tile(name, Content.Load<Texture2D>("Tiles/Grass/5"), TileCollision.Exit);
           
        }


        public void Update(GameTime gameTime)
        {

            keyboardState = Keyboard.GetState();
            player.Update(gameTime, keyboardState);

            if (panelarea != null)
            panelarea.Update(gameTime, this);

            if (messagebox != null)
                messagebox.Update(gameTime);

            UpdateEnemies(gameTime);
            UpdatePerson(gameTime);

            

        }

        public void UpdateEnemies(GameTime gameTime)
        {
            for (int i = 0; i < enemies.Count; ++i)
            {
                Enemy enemy = enemies[i];

                enemy.Update(gameTime, keyboardState);


            }

        }

        public void UpdatePerson(GameTime gameTime)
        {
            for (int i = 0; i < npcs.Count; ++i)
            {
                NPC npc = npcs[i];

                npc.Update(gameTime, keyboardState);


            }

        }


        public void UpdateObjects(GameTime gameTime)
         {
             for (int i = 0; i < objects.Count; ++i)
             {
                 Object obj = objects[i];

                 obj.Update(gameTime);

                 
             }

         }

        private void ScrollCamera(Viewport viewport)
        {


            // Calculate the edges of the screen.
            float marginWidth = viewport.Width / 2;
            float marginLeft = cameraPositionX + marginWidth;
            float marginRight = cameraPositionX + viewport.Width - marginWidth;
            float marginHeight = viewport.Height / 2;
            float marginTop = cameraPositionY + marginHeight;
            float marginBottom = cameraPositionY + viewport.Height - marginHeight;


            // Calculate how far to scroll when the player is near the edges of the screen.
            
            if (player.Position.X < marginLeft)
            {
                cameraMovementX = player.Position.X - marginWidth;
            }
            
            else if (player.Position.X > marginRight)
            {
                cameraMovementX = player.Position.X - marginWidth;
            }

            
            if (player.Position.Y < marginTop)
            {
                cameraMovementY = player.Position.Y - marginHeight;
            }
            else if (player.Position.X > marginBottom)
            {
                cameraMovementY = player.Position.Y - marginHeight;
            }

            // Update the camera position, but prevent scrolling off the ends of the level.
            float maxCameraPositionX = Tile.Width * Width - viewport.Width;
            cameraPositionX = MathHelper.Clamp(cameraMovementX, 0.0f, maxCameraPositionX);

            float maxCameraPositionY = Tile.Height * Height - viewport.Height;
            cameraPositionY = MathHelper.Clamp(cameraMovementY, 0.0f, maxCameraPositionY);

            cameraPosition = new Vector2(cameraPositionX, cameraPositionY);
        }
        
        public void Draw(GameTime gameTime, SpriteBatch spriteBatch)
        {


            //for (int i = 0; i <= EntityLayer; ++i)
                //spriteBatch.Draw(layers[i], Vector2.Zero, Color.White);
            
            DrawTiles(spriteBatch);
            ScrollCamera(spriteBatch.GraphicsDevice.Viewport);


            if (player != null)
            player.Draw(gameTime, spriteBatch);

            if (panelarea.isVisible == true)
            panelarea.Draw(spriteBatch, cameraPositionX);

            if (messagebox.isVisible == true)
                messagebox.Draw(spriteBatch, cameraPositionX);

            foreach (Object obj in objects)
                if (obj != null)
                obj.Draw(gameTime, spriteBatch);

            foreach (NPC npc in npcs)
                if (npc != null)
                    npc.Draw(gameTime, spriteBatch);

            foreach (Enemy enemy in enemies)
                if (enemy != null)
                    enemy.Draw(gameTime, spriteBatch);

        }

        /// <summary>
        /// Draws each tile in the level.
        /// </summary>
        private void DrawTiles(SpriteBatch spriteBatch)
        {
            // For each tile position
            for (int y = 0; y < Height; ++y)
            {
                for (int x = 0; x < Width; ++x)
                {
                    
                    if (tiles[x, y] != null)
                    {
                        Texture2D texture = tiles[x, y].Texture;
                        
                        // Draw it in screen space.
                        Vector2 position = new Vector2(x, y) * Tile.Size;
                        spriteBatch.Draw(texture, position, Color.White);
                        
                        
                    }
                }
            }
        }

    }
}
